package com.own.component.util.excel.util.write.method;

import com.own.component.util.excel.exception.ExcelException;
import com.own.component.util.excel.util.write.function.AfterSheetFunction;
import com.own.component.util.excel.util.write.function.AfterWorkbookFunction;
import com.own.component.util.excel.util.write.function.GetWorkbookFunction;
import com.own.component.util.excel.util.write.model.DataFormat;
import com.own.component.util.excel.util.write.model.Style;
import com.own.component.util.excel.util.write.record.CellRecord;
import com.own.component.util.excel.util.write.record.ExcelRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * ExcelUtil
 *
 * @author chenxueli
 * @date 2020/9/21 13:56
 */
@Slf4j
public final class ExcelWriteUtil {

    /**
     * 写入excel到文件
     *
     * @param excel                 表格对象
     * @param path                  路径
     * @param afterSheetFunction    sheet方法
     * @param afterWorkbookFunction workbook方法
     * @return 文件对象
     */
    public File write(ExcelRecord excel, String path, GetWorkbookFunction getWorkbookFunction, AfterSheetFunction afterSheetFunction, AfterWorkbookFunction afterWorkbookFunction) {
        var workbook = getWorkbookFunction.accept();
        var styleList = excel.getStyleList();
        log.debug("styleList的样式个数：{}", styleList.size());
        var styleMap = buildStyleMap(workbook, excel.getStyleList());
        // 遍历工作簿
        excel.getSheetList().forEach(book -> {
            var sheet = workbook.createSheet(book.getTitle());
            // 判断最大的行数与最大的列数
            int maxRow = book.getList().stream().max(Comparator.comparing(CellRecord::getY)).map(CellRecord::getY).orElse(0);
            // 写入所有的行数据
            for (var i = 0; i <= maxRow; i++) {
                sheet.createRow(i);
            }
            // 遍历单元格数据逐个写入
            book.getList().forEach(item -> {
                var row = sheet.getRow(item.getY());
                var cell = row.createCell(item.getX());
                // 设置值
                if (item.getFormula() != null && !item.getFormula().isEmpty()) {
                    cell.setCellType(CellType.FORMULA);
                    cell.setCellFormula(item.getFormula());
                } else {
                    // 如果当前有格式信息
                    if (item.getDataFormat() != null) {
                        item.getDataFormat().write(cell, item);
                    } else {
                        cell.setCellValue(item.getValue());
                    }
                }
                // 设置样式
                if (item.getStyle() != null) {
                    cell.setCellStyle(styleMap.get(item.getStyle()));
                }
                // 插入批注
                if (item.getComment() != null && !item.getComment().isEmpty()) {
                    var drawing = sheet.createDrawingPatriarch();
                    var comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, item.getX(), item.getY(), 0, 0));
                    // 输入批注信息
                    var textString = new XSSFRichTextString(item.getComment());
                    comment.setString(textString);
                    cell.setCellComment(comment);
                }
            });
            // todo 插入批注信息（批注为单独的xy数据）
            // 写入单元格合并数据
            book.getMergeList().forEach(item -> sheet.addMergedRegion(new CellRangeAddress(item.y1(), item.y2(), item.x1(), item.x2())));
            // 写入视图冻结数据
            if (book.getFreeze() != null) {
                sheet.createFreezePane(book.getFreeze().colSplit(), book.getFreeze().rowSplit(), book.getFreeze().leftMostColumn(), book.getFreeze().topRow());
            }
            // 设置高度
            for (var entry : book.getHeightMap().entrySet()) {
                sheet.getRow(entry.getKey()).setHeight(entry.getValue());
            }
            // 设置宽度（宽度计算：n * 256 + 184）
            for (var entry : book.getWidthMap().entrySet()) {
                sheet.setColumnWidth(entry.getKey(), entry.getValue() * 256 + 184);
            }
            if (afterSheetFunction != null) {
                afterSheetFunction.accept(sheet);
            }
        });
        if (afterWorkbookFunction != null) {
            afterWorkbookFunction.accept(workbook);
        }
        // 写入文件到临时文件
        var file = buildFile(excel.getFileName(), path);
        try (FileOutputStream out = new FileOutputStream(file)) {
            workbook.write(out);
        } catch (IOException e) {
            throw new ExcelException("文件写入错误");
        }
        return file;
    }

    /**
     * 构造表格所需要的单元格格式信息
     *
     * @param workbook 表格对象
     * @param map      格式列表
     * @return 单元格格式列表
     */
    private Map<String, Short> buildDataFormatList(XSSFWorkbook workbook, Map<String, DataFormat> map) {
        var resultMap = new HashMap<String, Short>(map.size() * 2);
        map.forEach((k, v) -> {
            resultMap.put(k, workbook.createDataFormat().getFormat(v.getFormat()));
        });
        return resultMap;
    }

    /**
     * 构造表格所需要的样式对象
     *
     * @param workbook  表格对象
     * @param styleList 原样式列表
     * @return 新样式列表
     */
    private Map<Style, XSSFCellStyle> buildStyleMap(XSSFWorkbook workbook, Collection<Style> styleList) {
        var fontMap = new HashMap<String, Font>(16);
        // 从list中获取写入格式信息
        var dataFormatList = styleList.stream().map(Style::getWriteFormat).filter(Objects::nonNull).toList();
        var map = new HashMap<String, Short>(Math.max(dataFormatList.size() * 2, 4));
        dataFormatList.forEach(item -> {
            map.put(item.format(), workbook.createDataFormat().getFormat(item.format()));
        });
        return styleList.stream().collect(Collectors.toMap(Function.identity(), item -> {
            var style = workbook.createCellStyle();
            if (item.getFont() != null) {
                style.setFont(item.getFont().get(workbook, fontMap));
            }
            if (item.getDataFormat() != null) {
                style.setDataFormat(item.getDataFormat());
            }
            if (item.getWriteFormat() != null) {
                style.setDataFormat(map.get(item.getWriteFormat().format()));
            }
            // 设置其他属性
            Optional.ofNullable(item.getStyleList()).ifPresent(list -> list.forEach(temp -> temp.apply(style)));
            return style;
        }));
    }

    /**
     * 获取写入的文件信息
     *
     * @param name 文件名
     * @return 文件信息
     */
    private File buildFile(String name) {
        return buildFile(name, null);
    }

    /**
     * 获取写入的文件信息
     *
     * @param name 文件名
     * @param path 路径
     * @return 文件信息
     */
    private File buildFile(String name, String path) {
        if (StringUtils.isBlank(path)) {
            try {
                return File.createTempFile(name, ".xlsx");
            } catch (IOException e) {
                throw new ExcelException("文件创建错误");
            }
        }
        return new File(path + File.separator + name + ".xlsx");
    }

}
